#pragma 
#include<iostream>
#include<queue>
#include<pthread.h>
#include<unistd.h>

using namespace std;

static const int MAX = 5;

template<class T>
class BlockQueue
{
public:
    BlockQueue(const int& maxcap = MAX)
    :_maxcap(maxcap)
    {
        //初始化锁和条件变量
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }

    ~BlockQueue()
    {
        //销毁锁和条件变量
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }

    //插入数据——生产
    void push(const T& data) // 输入型参数一般是const&
    {
        pthread_mutex_lock(&_mutex); // 加锁
        
        // 细节2：这里不能用if，而要用while。
        // 1.pthread_cond_wait可能调用失败！失败了不能再往后继续执行
        // 2.因为当生产者线程很多的时候，全部因为pthread_cond_wait被挂起的线程当它重新回来的时候
        // 就会继续向后执行，很有可能导致push多插入——多生产了
        while(is_full()) // 要判断队列是否位满，满了无法插入，要阻塞
        {
            //生产者线程要阻塞，直至队列有空位，可以插入——生产的时候会被唤醒
            pthread_cond_wait(&_pcond, &_mutex); 
            // 细节2：pthread_cond_wait一定要传当前线程的一个互斥锁进去作为参数
            // 1.当调用pthread_cond_wait时，会先解锁(原子操作)，在将自己挂起
            // 2.当pthread_cond_wait返回时，会自动获取之前所传入的锁【申请锁，成功后才能向后运行】
        }
        // 走到这里，就是可以生产——插入
        _q.push(data);
        pthread_cond_signal(&_ccond); //唤醒消费者线程来消费【这里的唤醒可以有一定的策略】
        //细节3：这个pthread_cond_signal可以放在临界区内，也可以放在临界区后。
        // 但是建议放在临界区内

        pthread_mutex_unlock(&_mutex); //解锁
    }

    //弹出数据——消费
    void pop(T* out) // 输出型参数不带const，一般是*。输入输出型一般是&
    {
        pthread_mutex_lock(&_mutex); //加锁
        // 判断当前队列是否为空
        while(is_empty())
        {
            //队列为空，无法消费，消费者线程要阻塞，直至有数据可以消费，会被唤醒
            pthread_cond_wait(&_ccond, &_mutex); 
        }
        //走到这里就是可以消费了
        *out = _q.front(); //将消费的数据输出回去
        _q.pop();
        // 只要消费了一定有至少一个空位，唤醒生产者线程
        pthread_cond_signal(&_pcond); //可设置一定的策略

        pthread_mutex_unlock(&_mutex); //解锁
    }

private:
    bool is_empty()
    {
        return _q.empty();
    }

    bool is_full()
    {
        // 判断是否和阻塞队列上限个数相同
        if(_q.size() == _maxcap)
            return true;
        
        return false;
    }
private:
    queue<T> _q;
    int _maxcap; //队列的上限
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond; // 生产者对应的条件变量
    pthread_cond_t _ccond; // 消费者对应的条件变量
};
